#include <gtest/gtest.h>
#include <cb/utility/serializable.hpp>
#include <cb/utility/serialization.hpp>
#include <cb/utility/unique_object.hpp>
#include <array>

TEST(SerializationTest, SerializableClassification) {
    USING_CB_NAMESPACE
    EXPECT_TRUE(TIsSerializable<int>::value) << "int should be serializable";
    EXPECT_TRUE(TIsSerializable<float>::value) << "float should be serializable";
    EXPECT_FALSE(TIsBasicSerializable<std::vector<double>>::value) << "vector of double should not be basic serializable";
    EXPECT_TRUE(TIsSerializable<std::vector<double>>::value) << "vector of double should be serializable";
    EXPECT_TRUE(TIsSerializable<std::vector<std::vector<double>>>::value) << "vector of vector of double should be serializable";
    struct SomePOD {
        int a;
        float b;
        char c[100];
    };
    EXPECT_TRUE(TIsSerializable<SomePOD>::value) << "POD should be serializable";
    class SomeClass {
    public:
        int a;
        float b;
        char c[100];
        SomeClass() {
            memset(c, 0, sizeof c);
        }
        ~SomeClass() {}
    };
    EXPECT_FALSE(TIsSerializable<SomeClass>::value) << "Classes that does not inherit from Serializable are not serializables";

    class SomeSerializableClass : public Serializable {
    public:
        int a;
        float b;
        char c[100];
        SomeSerializableClass() {
            memset(c, 0, sizeof c);
        }
        ~SomeSerializableClass() {}
        void Serialize(std::ostream &os) const override {
            os.write(reinterpret_cast<const char *>(&a), sizeof(a));
            os.write(reinterpret_cast<const char *>(&b), sizeof(b));
            os.write(reinterpret_cast<const char *>(&c), sizeof(c));
        }
        void Deserialize(std::istream &is) override {
            is.read(reinterpret_cast<char *>(&a), sizeof(a));
            is.read(reinterpret_cast<char *>(&b), sizeof(b));
            is.read(reinterpret_cast<char *>(&c), sizeof(c));
        }
    };
    EXPECT_TRUE(TIsSerializable<SomeSerializableClass>::value) << "Classes that inherit from Serializable are serializables";
    // std::array and C array matches the same type & same template function.
    EXPECT_TRUE(TIsSerializable<SomeSerializableClass[12]>::value) << "array of SomeSerializableClass should be serializable";
    using STLArrayOfSomeSerializableClass = std::array<SomeSerializableClass, 12>;
    EXPECT_TRUE(TIsSerializable<STLArrayOfSomeSerializableClass>::value) << "std::array of SomeSerializableClass should be serializable";
}


TEST(SerializationTest, BasicSerialization) {
    USING_CB_NAMESPACE
    {
        int x = 123123;
        std::stringstream ss;
        Serialize(ss, x);
        int x_out = 0;
        Deserialize(ss, x_out);
        EXPECT_EQ(x, x_out) << "Serialization and deserialization should be the same";
    }
    {
        struct SomePOD {
            int a;
            float b;
            char c[100];
            uint64_t v;
        };
        SomePOD x = {123123, 123.123, "123123", 123123123123123123};
        std::stringstream ss;
        Serialize(ss, x);
        SomePOD x_out = {};
        Deserialize(ss, x_out);
        EXPECT_EQ(x.a, x_out.a) << "Serialization and deserialization of PODs should be the same";
    }
    {
        std::string string_to_serialize = "abcdeaaaa";
        std::string_view string_view_to_serialize = std::string_view (string_to_serialize.begin(), string_to_serialize.begin() + 4);
        std::stringstream ss;
        Serialize(ss, string_to_serialize);
        std::string string_to_deserialize;
        Deserialize(ss, string_to_deserialize);
        EXPECT_EQ(string_to_serialize, string_to_deserialize) << "Serialization and deserialization of strings should be the same";
        Serialize(ss, string_view_to_serialize);
        Deserialize(ss, string_to_deserialize);
        EXPECT_EQ(string_view_to_serialize, string_to_deserialize) << "Serialization and deserialization of string views should be the same";
    }

}
class SomeUniqueObjectWithoutDataTypeMeta : public CB_NAMESPACE::UniqueObject {
public:
    int a;
    float b;
};
class SomeUniqueObject : public CB_NAMESPACE::UniqueObject {
public:
    int a;
    float b;
//CB_DATATYPE_DECL(SomeUniqueObject, 0);
public:
    constexpr static char kTypeName[] = "SomeUniqueObject";
    constexpr static auto kDataTypeMeta = ::cb::TMakeDataTypeMeta<kTypeName, 0>::value;
};


TEST(SerializationTest, SerializablePtr1) {
    USING_CB_NAMESPACE
    {
        EXPECT_FALSE(THasDataTypeMeta<SomeUniqueObjectWithoutDataTypeMeta>::value) << "SomeUniqueObjectWithoutDataTypeMeta should not have data type meta";


        auto X = UniqueObjectRegistry::Get().CreateAndRegisterObject<SomeUniqueObject>();
        auto Y = UniqueObjectRegistry::Get().CreateAndRegisterObject<SomeUniqueObject>();
        auto Z = UniqueObjectRegistry::Get().CreateAndRegisterObject<SomeUniqueObject>();
        EXPECT_TRUE(THasDataTypeMeta<SomeUniqueObject>::value) << "SomeUniqueObject should have data type meta";
        TSharedSerializablePtr<SomeUniqueObject> ptr = UniqueObjectRegistry::Get().CreateAndRegisterObject<SomeUniqueObject>();
        TWeakSerializablePtr<SomeUniqueObject> ptr_weak = ptr;
        EXPECT_EQ(ptr_weak.Lock().Get(), ptr.Get()) << "Weak ptr should equals to the original ptr";
        ptr->a = 123123;
        ptr->b = 123.123;
        std::stringstream ss;
        Serialize(ss, ptr);
        TSharedSerializablePtr<SomeUniqueObject> ptr_out;
        Deserialize(ss, ptr_out);
        EXPECT_EQ(ptr->a, 123123) << "The value should be the same as the assigned one";
        EXPECT_EQ(ptr->a, ptr_out->a) << "Serialization and deserialization of serializable ptrs should be the same";
        EXPECT_EQ(ptr.Get(), ptr_out.Get()) << "Serialization and deserialization of serializable ptrs should be the same";
    }
}

TEST(SerializationTest, SerializablePOD) {
    USING_CB_NAMESPACE
    {
        struct SomePOD {
            int a;
            float b;
            char c[100];
            uint64_t v;
        };
        SomePOD x = {123123, 123.123, "123123", 123123123123123123};
        std::stringstream ss;
        Serialize(ss, x);
        SomePOD x_out = {};
        Deserialize(ss, x_out);
        EXPECT_EQ(x.a, x_out.a) << "Serialization and deserialization of PODs should be the same";
        EXPECT_EQ(x.b, x_out.b) << "Serialization and deserialization of PODs should be the same";
        EXPECT_TRUE(strcmp(x.c, x_out.c) == 0) << "Serialization and deserialization of PODs should be the same";
        EXPECT_EQ(x.v, x_out.v) << "Serialization and deserialization of PODs should be the same";
    }
}

TEST(SerializationTest, SerializableArray) {
    USING_CB_NAMESPACE
    {
        std::array<int, 10> x = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        std::stringstream ss;
        Serialize(ss, x);
        std::array<int, 10> x_out {};
        Deserialize(ss, x_out);
        EXPECT_EQ(x, x_out) << "Serialization and deserialization of arrays should be the same";
    }
    {
        std::array<std::array<int, 10>, 10> x {};
        for(int i = 0; i < 10; ++i) {
            for(int j = 0; j < 10; ++j) {
                x[i][j] = i * 10 + j;
            }
        }
        std::stringstream ss;
        Serialize(ss, x);
        std::array<std::array<int, 10>, 10> x_out {};
        Deserialize(ss, x_out);
        EXPECT_EQ(x, x_out) << "Serialization and deserialization of arrays should be the same";
    }
}


TEST(SerializationTest, SerializablePtr2) {
    USING_CB_NAMESPACE

    // Create unique objects and assign them to shared and weak pointers
    auto X = UniqueObjectRegistry::Get().CreateAndRegisterObject<SomeUniqueObject>();
    auto Y = UniqueObjectRegistry::Get().CreateAndRegisterObject<SomeUniqueObject>();
    auto Z = UniqueObjectRegistry::Get().CreateAndRegisterObject<SomeUniqueObject>();

    TSharedSerializablePtr<SomeUniqueObject> ptr_X = X;
    TWeakSerializablePtr<SomeUniqueObject> ptr_weak_X = X;

    TSharedSerializablePtr<SomeUniqueObject> ptr_Y = Y;
    TWeakSerializablePtr<SomeUniqueObject> ptr_weak_Y = Y;

    TSharedSerializablePtr<SomeUniqueObject> ptr_Z = Z;
    TWeakSerializablePtr<SomeUniqueObject> ptr_weak_Z = Z;

    // Serialize the pointers
    std::stringstream ss;
    Serialize(ss, ptr_X);
    Serialize(ss, ptr_Y);
    Serialize(ss, ptr_Z);

    // Deserialize the pointers
    TSharedSerializablePtr<SomeUniqueObject> ptr_X_out;
    TSharedSerializablePtr<SomeUniqueObject> ptr_Y_out;
    TSharedSerializablePtr<SomeUniqueObject> ptr_Z_out;

    Deserialize(ss, ptr_X_out);
    Deserialize(ss, ptr_Y_out);
    Deserialize(ss, ptr_Z_out);

    // Check if the original pointers and the deserialized pointers are pointing to the same objects
    EXPECT_EQ(ptr_X.Get(), ptr_X_out.Get()) << "Serialization and deserialization of serializable ptrs should be the same";
    EXPECT_EQ(ptr_Y.Get(), ptr_Y_out.Get()) << "Serialization and deserialization of serializable ptrs should be the same";
    EXPECT_EQ(ptr_Z.Get(), ptr_Z_out.Get()) << "Serialization and deserialization of serializable ptrs should be the same";

    // Check if the original weak pointers and the deserialized weak pointers are pointing to the same objects
    EXPECT_EQ(ptr_weak_X.Lock().Get(), ptr_X_out.Get()) << "Serialization and deserialization of serializable ptrs should be the same";
    EXPECT_EQ(ptr_weak_Y.Lock().Get(), ptr_Y_out.Get()) << "Serialization and deserialization of serializable ptrs should be the same";
    EXPECT_EQ(ptr_weak_Z.Lock().Get(), ptr_Z_out.Get()) << "Serialization and deserialization of serializable ptrs should be the same";
}

TEST(SerializationTest, SerializablePtrCopyMove) {
    USING_CB_NAMESPACE

    // Create unique objects
    auto X = UniqueObjectRegistry::Get().CreateAndRegisterObject<SomeUniqueObject>();
    auto Y = UniqueObjectRegistry::Get().CreateAndRegisterObject<SomeUniqueObject>();

    // Assign them to shared and weak pointers
    TSharedSerializablePtr<SomeUniqueObject> ptr_X = X;
    TWeakSerializablePtr<SomeUniqueObject> ptr_weak_X = X;

    // Copy assignment
    TSharedSerializablePtr<SomeUniqueObject> ptr_X_copy = ptr_X;
    TWeakSerializablePtr<SomeUniqueObject> ptr_weak_X_copy = ptr_weak_X;

    // Check if the original pointers and the copied pointers are pointing to the same objects
    EXPECT_EQ(ptr_X.Get(), ptr_X_copy.Get()) << "Copy assignment should result in pointers pointing to the same object";
    EXPECT_EQ(ptr_weak_X.Lock().Get(), ptr_weak_X_copy.Lock().Get()) << "Copy assignment should result in pointers pointing to the same object";

    // Move assignment
    TSharedSerializablePtr<SomeUniqueObject> ptr_X_move = std::move(ptr_X_copy);
    TWeakSerializablePtr<SomeUniqueObject> ptr_weak_X_move = std::move(ptr_weak_X_copy);

    // Check if the original pointers and the moved pointers are pointing to the same objects
    EXPECT_EQ(ptr_X.Get(), ptr_X_move.Get()) << "Move assignment should result in pointers pointing to the same object";
    EXPECT_EQ(ptr_weak_X.Lock().Get(), ptr_weak_X_move.Lock().Get()) << "Move assignment should result in pointers pointing to the same object";

    // Copy construct
    TSharedSerializablePtr<SomeUniqueObject> ptr_X_copy_construct(ptr_X);
    TWeakSerializablePtr<SomeUniqueObject> ptr_weak_X_copy_construct(ptr_weak_X);

    // Check if the original pointers and the copy constructed pointers are pointing to the same objects
    EXPECT_EQ(ptr_X.Get(), ptr_X_copy_construct.Get()) << "Copy construct should result in pointers pointing to the same object";
    EXPECT_EQ(ptr_weak_X.Lock().Get(), ptr_weak_X_copy_construct.Lock().Get()) << "Copy construct should result in pointers pointing to the same object";

    // Move construct
    TSharedSerializablePtr<SomeUniqueObject> ptr_X_move_construct(std::move(ptr_X_copy_construct));
    TWeakSerializablePtr<SomeUniqueObject> ptr_weak_X_move_construct(std::move(ptr_weak_X_copy_construct));

    // Check if the original pointers and the move constructed pointers are pointing to the same objects
    EXPECT_EQ(ptr_X.Get(), ptr_X_move_construct.Get()) << "Move construct should result in pointers pointing to the same object";
    EXPECT_EQ(ptr_weak_X.Lock().Get(), ptr_weak_X_move_construct.Lock().Get()) << "Move construct should result in pointers pointing to the same object";
}

TEST(SerializationTest, SerializablePtrSTL) {
    USING_CB_NAMESPACE

    // Create unique objects
    auto X = UniqueObjectRegistry::Get().CreateAndRegisterObject<SomeUniqueObject>();

    // Assign them to shared and weak pointers
    TSharedSerializablePtr<SomeUniqueObject> ptr_X = X;
    TWeakSerializablePtr<SomeUniqueObject> ptr_weak_X = X;

    // Create std::shared_ptr and std::weak_ptr from SerializablePtrs
    std::shared_ptr<SomeUniqueObject> std_ptr_X = ptr_X.GetSharedPtr();
    std::weak_ptr<SomeUniqueObject> std_weak_ptr_X = ptr_weak_X.GetWeakPtr();

    // Check if the original pointers and the std pointers are pointing to the same objects
    EXPECT_EQ(ptr_X.Get(), std_ptr_X.get()) << "TSharedSerializablePtr should be compatible with std::shared_ptr";
    EXPECT_EQ(ptr_weak_X.Lock().Get(), std_weak_ptr_X.lock().get()) << "TWeakSerializablePtr should be compatible with std::weak_ptr";

    // Create SerializablePtrs from std::shared_ptr and std::weak_ptr
    TSharedSerializablePtr<SomeUniqueObject> ptr_X_from_std = std_ptr_X;
    TWeakSerializablePtr<SomeUniqueObject> ptr_weak_X_from_std = std_weak_ptr_X;

    // Check if the std pointers and the SerializablePtrs are pointing to the same objects
    EXPECT_EQ(std_ptr_X.get(), ptr_X_from_std.Get()) << "std::shared_ptr should be compatible with TSharedSerializablePtr";
    EXPECT_EQ(std_weak_ptr_X.lock().get(), ptr_weak_X_from_std.Lock().Get()) << "std::weak_ptr should be compatible with TWeakSerializablePtr";
}

TEST(SerializationTest, STLContainerSerialization1) {
    USING_CB_NAMESPACE

    // Test std::vector
    {
        std::vector<TSharedSerializablePtr<SomeUniqueObject>> vec;
        for (int i = 0; i < 10; ++i) {
            vec.emplace_back(UniqueObjectRegistry::Get().CreateAndRegisterObject<SomeUniqueObject>());
        }

        std::stringstream ss;
        Serialize(ss, vec);

        std::vector<TSharedSerializablePtr<SomeUniqueObject>> vec_out;
        Deserialize(ss, vec_out);

        EXPECT_EQ(vec.size(), vec_out.size()) << "Serialized and deserialized vectors should have the same size";

        for (size_t i = 0; i < vec.size(); ++i) {
            EXPECT_EQ(vec[i].Get(), vec_out[i].Get()) << "Serialized and deserialized vectors should have the same elements";
        }
    }

    // Test std::map
    {
        std::map<int, TSharedSerializablePtr<SomeUniqueObject>> map;
        for (int i = 0; i < 10; ++i) {
            map[i] = UniqueObjectRegistry::Get().CreateAndRegisterObject<SomeUniqueObject>();
        }

        std::stringstream ss;
        Serialize(ss, map);

        std::map<int, TSharedSerializablePtr<SomeUniqueObject>> map_out;
        // Clang pops up an error here. IDN W.
        Deserialize(ss, map_out);

        EXPECT_EQ(map.size(), map_out.size()) << "Serialized and deserialized maps should have the same size";

        for (const auto& pair : map) {
            EXPECT_EQ(pair.second.Get(), map_out[pair.first].Get()) << "Serialized and deserialized maps should have the same elements";
        }
    }

    // Test std::array
    {
        std::array<TSharedSerializablePtr<SomeUniqueObject>, 10> arr;
        for (int i = 0; i < 10; ++i) {
            arr[i] = UniqueObjectRegistry::Get().CreateAndRegisterObject<SomeUniqueObject>();
        }

        std::stringstream ss;
        Serialize(ss, arr);

        std::array<TSharedSerializablePtr<SomeUniqueObject>, 10> arr_out;
        Deserialize(ss, arr_out);

        for (size_t i = 0; i < arr.size(); ++i) {
            EXPECT_EQ(arr[i].Get(), arr_out[i].Get()) << "Serialized and deserialized arrays should have the same elements";
        }
    }
}

TEST(SerializationTest, STLContainerSerialization2) {
    USING_CB_NAMESPACE

    // Define a POD
    struct SomePOD {
        int a;
        float b;
        char c[100];
    };

    // Test std::vector
    {
        std::vector<SomePOD> vec;
        for (int i = 0; i < 10; ++i) {
            SomePOD pod = {i, static_cast<float>(i), "test"};
            vec.push_back(pod);
        }

        std::stringstream ss;
        Serialize(ss, vec);

        std::vector<SomePOD> vec_out;
        Deserialize(ss, vec_out);

        EXPECT_EQ(vec.size(), vec_out.size()) << "Serialized and deserialized vectors should have the same size";

        for (size_t i = 0; i < vec.size(); ++i) {
            EXPECT_EQ(vec[i].a, vec_out[i].a) << "Serialized and deserialized vectors should have the same elements";
            EXPECT_EQ(vec[i].b, vec_out[i].b) << "Serialized and deserialized vectors should have the same elements";
            EXPECT_STREQ(vec[i].c, vec_out[i].c) << "Serialized and deserialized vectors should have the same elements";
        }
    }

    // Test std::map
    {
        std::map<int, SomePOD> map;
        for (int i = 0; i < 10; ++i) {
            SomePOD pod = {i, static_cast<float>(i), "test"};
            map[i] = pod;
        }

        std::stringstream ss;
        Serialize(ss, map);

        std::map<int, SomePOD> map_out;
        Deserialize(ss, map_out);

        EXPECT_EQ(map.size(), map_out.size()) << "Serialized and deserialized maps should have the same size";

        for (const auto& pair : map) {
            EXPECT_EQ(pair.second.a, map_out[pair.first].a) << "Serialized and deserialized maps should have the same elements";
            EXPECT_EQ(pair.second.b, map_out[pair.first].b) << "Serialized and deserialized maps should have the same elements";
            EXPECT_STREQ(pair.second.c, map_out[pair.first].c) << "Serialized and deserialized maps should have the same elements";
        }
    }

    // Test std::array
    {
        std::array<SomePOD, 10> arr;
        for (int i = 0; i < 10; ++i) {
            SomePOD pod = {i, static_cast<float>(i), "test"};
            arr[i] = pod;
        }

        std::stringstream ss;
        Serialize(ss, arr);

        std::array<SomePOD, 10> arr_out;
        Deserialize(ss, arr_out);

        for (size_t i = 0; i < arr.size(); ++i) {
            EXPECT_EQ(arr[i].a, arr_out[i].a) << "Serialized and deserialized arrays should have the same elements";
            EXPECT_EQ(arr[i].b, arr_out[i].b) << "Serialized and deserialized arrays should have the same elements";
            EXPECT_STREQ(arr[i].c, arr_out[i].c) << "Serialized and deserialized arrays should have the same elements";
        }
    }
}

class TestObject : public CB_NAMESPACE::Serializable, public CB_NAMESPACE::UniqueObject {
public:
    int a;
    float b;
    std::string c;

    TestObject() : a(0), b(0.0f), c("") {}

    TestObject(int a, float b, const std::string& c) : a(a), b(b), c(c) {}

    void Serialize(std::ostream &os) const override {
        os << std::setprecision(10) << a << ' ' << b << ' ' << c << '\n';
    }

    void Deserialize(std::istream &is) override {
        is >> a >> b >> c;
    }

    constexpr static char kTypeName[] = "TestObject";
    constexpr static auto kDataTypeMeta = ::CB_NAMESPACE::TMakeDataTypeMeta<kTypeName, 0>::value;
};

TEST(SerializationTest, ObjectSerialization) {
    USING_CB_NAMESPACE

    // Create and register a bunch of TestObject instances with random values
    std::vector<TSharedSerializablePtr<TestObject>> objects;
    for (int i = 0; i < 10; ++i) {
        int a = rand() % 100;
        float b = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
        std::string c = std::to_string(rand() % 100);

        auto object = UniqueObjectRegistry::Get().CreateAndRegisterObject<TestObject>(a, b, c);
        objects.emplace_back(std::move(object));
    }

    // Serialize the objects
    std::stringstream ss;
    for (const auto& object : objects) {
        object->Serialize(ss);
    }

    // Continue with deserialization test
    std::vector<TSharedSerializablePtr<TestObject>> deserialized_objects;
    for (int i = 0; i < 10; ++i) {
        auto deserialized_object = UniqueObjectRegistry::Get().CreateAndRegisterObject<TestObject>();
        deserialized_object->Deserialize(ss);
        deserialized_objects.emplace_back(std::move(deserialized_object));
    }

    // Compare the original and deserialized objects
    for (int i = 0; i < 10; ++i) {
        EXPECT_EQ(objects[i]->a, deserialized_objects[i]->a);
        EXPECT_EQ(objects[i]->b, deserialized_objects[i]->b);
        EXPECT_EQ(objects[i]->c, deserialized_objects[i]->c);
    }

}

TEST(SerializationTest, TestObjectPtrSerializationWithRelease) {
    USING_CB_NAMESPACE

    // Create and register a bunch of TestObject instances with random values
    std::vector<TSharedSerializablePtr<TestObject>> objects;
    for (int i = 0; i < 10; ++i) {
        int a = rand() % 100;
        float b = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
        std::string c = std::to_string(rand() % 100);

        auto object = UniqueObjectRegistry::Get().CreateAndRegisterObject<TestObject>(a, b, c);
        objects.push_back(object);
    }

    // Create a bunch of TSharedSerializablePtr<TestObject> and assign them with random TestObject instances
    std::vector<TSharedSerializablePtr<TestObject>> ptrs;
    for (int i = 0; i < 10; ++i) {
        ptrs.push_back(objects[rand() % 10]);
    }

    // Serialize the pointers
    std::stringstream ss;
    for (const auto& ptr : ptrs) {
        Serialize(ss, ptr);
    }

    // Release some random TestObject instances
    for (int i = 0; i < 5; ++i) {
        auto index = rand() % 10;
        objects[index].Reset();
        ptrs[index].Reset();
    }

    // Deserialize the pointers
    std::vector<TSharedSerializablePtr<TestObject>> deserialized_ptrs;
    for (int i = 0; i < 10; ++i) {
        TSharedSerializablePtr<TestObject> deserialized_ptr;
        Deserialize(ss, deserialized_ptr);
        deserialized_ptrs.push_back(deserialized_ptr);
    }

    // Compare the original and deserialized pointers
    for (int i = 0; i < 10; ++i) {
        if (!ptrs[i]) {
            // The following line should always trigger the 'use before load or released' assertion.
            // EXPECT_EQ(deserialized_ptrs[i].Get(), nullptr);
        } else {
            EXPECT_EQ(ptrs[i].Get(), deserialized_ptrs[i].Get());
        }
    }
}